#include <kermit.h>
#include <stdio.h>

#define KERM_ACK_LEN	8
#define KERM_NACK_LEN	8

#define tochar(x)		(((x) + 32) & 0xff)
#define unchar(x)		(((x) - 32) & 0xff)

static void kermit_send_ack(uint32_t seq)
{
    char buf[KERM_ACK_LEN];
    uint32_t sum = 0;

    buf[0] = KERM_START_CHAR;
    buf[1] = tochar(3);
    buf[2] = tochar(seq);
    buf[3] = KERM_TYPE_ACK;
	sum = buf[1] + buf[2] + buf[3];
	buf[4] = tochar ((sum + ((sum >> 6) & 0x03)) & 0x3f);
	buf[5] = KERM_END_CHAR;
	buf[6] = '\0';

    puts(buf);
}

static void kermit_send_nack(uint32_t seq)
{
    char buf[KERM_NACK_LEN];
    uint32_t sum = 0;

    buf[0] = KERM_START_CHAR;
    buf[1] = tochar(3);
    buf[2] = tochar(seq);
    buf[3] = KERM_TYPE_NACK;
	sum = buf[1] + buf[2] + buf[3];
	buf[4] = tochar ((sum + ((sum >> 6) & 0x03)) & 0x3f);
	buf[5] = KERM_END_CHAR;
	buf[6] = '\0';

    puts(buf);
}

unsigned int kermit_recv(void *load_addr)
{
	uint32_t new_char, sum, type, len_hi, len_lo, escape, seq;
	int32_t length;
	uint8_t *addr = (uint8_t *)load_addr, *old_addr;

	do {
		/* wait for the starting character or ^C */
		while (1) {
			new_char = getchar();
			if (new_char ==  KERM_START_CHAR) 
				break;
			if (new_char == KERM_EXIT_CHAR)
				return addr - (uint8_t *)load_addr;
		}		

		old_addr = addr;

		/* get length of packet */
		sum = 0;
		new_char = getchar();
		if (new_char < 32) 
			goto packet_error;
		sum += new_char;
		length = unchar(new_char);
		
		/* get sequence number */
		new_char = getchar();
		if (new_char < 32) 
			goto packet_error;
		sum += new_char;
		seq = unchar(new_char);
		length--;

		/* get packet type */
		new_char = getchar();
		type = new_char;
		sum += type;
		length--;
		
		if (length == -2) {
			/* get the two length bytes */
			new_char = getchar();
			if (new_char < 32) 
				goto packet_error;
			sum += new_char;
			len_hi = unchar(new_char);
			
			new_char = getchar();
			if (new_char < 32) 
				goto packet_error;
			sum += new_char;
			len_lo = unchar(new_char);

			length = len_hi * 95 + len_lo;
			
			/* check header checksum */
			new_char = getchar();
			if (new_char < 32) 
				goto packet_error;
            if (new_char != tochar ((sum + ((sum >> 6) & 0x03)) & 0x3f))
                goto packet_error;
			sum += new_char;
		}

		/* get data */
		escape = 0;
        while (length > 1) {
			new_char = getchar();
			if (new_char < 32) 
				goto packet_error;
			sum += new_char;
			length--;
            if (type != KERM_TYPE_DATA)
                continue;
			if (escape) {
				if ((new_char & 0x60) == 0x40) {
					new_char &= ~0x40;
				} else if ((new_char & 0x7f) == 0x3f) {
					new_char |= 0x40;
				}
				escape = 0;
			} else if (new_char == KERM_ESCAPE_CHAR) {
                escape = 1;
				continue;
            }
			
            *addr++ = new_char;
        }

        /* check checksum */
        new_char = getchar();
		if (new_char < 32)
			goto packet_error;
        if (new_char != tochar((sum + ((sum >> 6) & 0x03)) & 0x3f))
			goto packet_error;

		/* terminator */
		new_char = getchar();
		if (new_char != KERM_END_CHAR) {
packet_error:
			kermit_send_nack(seq);
			addr = old_addr;
			continue;
		}

        /* send ack package */
		kermit_send_ack(seq);
    } while (type != KERM_TYPE_BREAK);

    return addr - (uint8_t *)load_addr;
}

